/*
 * Wazuh cmdLineParser
 * Copyright (C) 2015, Wazuh Inc.
 * October 24, 2023.
 *
 * This program is free software; you can redistribute it
 * and/or modify it under the terms of the GNU General Public
 * License (version 2) as published by the FSF - Free Software
 * Foundation.
 */

#ifndef _CMD_ARGS_PARSER_HPP_
#define _CMD_ARGS_PARSER_HPP_

#include "json.hpp"
#include <fstream>
#include <iostream>
#include <string>
#include <vector>

/**
 * @brief Class to parse command line arguments.
 */
class CmdLineArgs
{
public:
    /**
     * @brief Constructor for CmdLineArgs.
     * @param argc Number of arguments.
     * @param argv Arguments.
     */
    explicit CmdLineArgs(const int argc, const char* argv[])
        : m_configurationFilePath {paramValueOf(argc, argv, "-c")}
        , m_templateFilePath {paramValueOf(argc, argv, "-t", std::make_pair(false, ""))}
        , m_waitTime {paramValueOf(argc, argv, "-s", std::make_pair(false, "0"))}
        , m_inputFiles {splitActions(paramValueOf(argc, argv, "-i", std::make_pair(false, "")))}
        , m_onlyDownloadContent {paramExists(argc, argv, "-d", std::make_pair(false, false))}
        , m_disableContentUpdater {paramExists(argc, argv, "-u", std::make_pair(false, false))}
        , m_fakeReportServer {paramExists(argc, argv, "-r", std::make_pair(false, false))}
        , m_fakeDBServer {paramValueOf(argc, argv, "-b", std::make_pair(false, ""))}
        , m_logFilePath {paramValueOf(argc, argv, "-l", std::make_pair(false, ""))}
        , m_fakeDBGlobal {paramValueOf(argc, argv, "-g", std::make_pair(false, ""))}
        , m_fakeDBPkgs {paramValueOf(argc, argv, "-p", std::make_pair(false, ""))}
        , m_fakeDBHotfixes {paramValueOf(argc, argv, "-h", std::make_pair(false, ""))}
    {
        // If only download content is enabled, check for other flags
        if (m_onlyDownloadContent)
        {
            if (getWaitTime() != 0 || !m_inputFiles.empty())
            {
                throw std::runtime_error(
                    "Error: When -d flag is used, only -t and -c flags can be used at the same time.");
            }
        }
    }

    /**
     * @brief Gets the configuration file path.
     * @return Configuration file path.
     */
    const std::string& getConfigurationFilePath() const
    {
        return m_configurationFilePath;
    }

    /**
     * @brief Gets the template file path.
     * @return Template file path.
     */
    const std::string& getTemplateFilePath() const
    {
        return m_templateFilePath;
    }

    /**
     * @brief Gets the wait time.
     * @return Wait time.
     */
    size_t getWaitTime() const
    {
        return std::stoull(m_waitTime);
    }

    /**
     * @brief Gets the input files.
     * @return Input files.
     */
    const std::vector<std::string>& getInputFiles() const
    {
        return m_inputFiles;
    }

    /**
     * @brief Gets whether only the content should be downloaded.
     * @return true or false
     */
    bool getOnlyDownloadContent() const
    {
        return m_onlyDownloadContent;
    }

    /**
     * @brief Gets whether the report server should be faked.
     *
     * @return true or false according to the value of the variable.
     */
    bool getFakeReportServer() const
    {
        return m_fakeReportServer;
    }

    /**
     * @brief Gets the fake agent data file path.
     *
     * @return Path to the json file with fake agent data.
     */
    const std::string& getFakeDBServer() const
    {
        return m_fakeDBServer;
    }

    /**
     * @brief Gets the fake agent packages data file path.
     *
     * @return Path to the json file with fake agent data.
     */
    const std::string& getFakeDBPkgs() const
    {
        return m_fakeDBPkgs;
    }

    /**
     * @brief Gets the fake agent hotfixes data file path.
     *
     * @return Path to the json file with fake agent data.
     */
    const std::string& getFakeDBHotfixes() const
    {
        return m_fakeDBHotfixes;
    }

    /**
     * @brief Gets the fake global data file path.
     *
     * @return Path to the json file with fake global data.
     */
    const std::string& getFakeDBGlobal() const
    {
        return m_fakeDBGlobal;
    }

    /**
     * @brief Gets the log file path.
     *
     * @return Path to the log file.
     */
    const std::string& getLogFilePath() const
    {
        return m_logFilePath;
    }

    /**
     * @brief Gets whether the content updater should be initialized.
     *
     * @return true or false according to the value of the variable.
     */
    bool getDisableContentUpdater() const
    {
        return m_disableContentUpdater;
    }

    /**
     * @brief Shows the help to the user.
     */
    static void showHelp()
    {
        std::cout << "\nUsage: vd_scanner_testtool <option(s)>\n"
                  << "Options:\n"
                  << "\t-h \t\t\tShow this help message\n"
                  << "\t-c CONFIG_FILE\t\tSpecifies the configuration file.\n"
                  << "\t-t TEMPLATE_FILE\tSpecifies the template file.\n"
                  << "\t-s WAIT_TIME\t\tSpecifies the wait before exit.\n"
                  << "\t-d \t\t\tOnly download content.\n"
                  << "\t-r \t\t\tFake report server that will receive the alerts messages.\n"
                  << "\t-b FAKE_DB_OS_FILE\t\tFake DB JSON file with the mocked agent os data.\n"
                  << "\t-g FAKE_GLOBAL_DB_FILE\t\tFake DB JSON file with the mocked global data.\n"
                  << "\t-p FAKE_DB_PKGS_FILE\t\tFake DB JSON file with the mocked agent packages data.\n"
                  << "\t-h FAKE_DB_HOTFIXES_FILE\t\tFake DB JSON file with the mocked agent hotfixes data.\n"
                  << "\t-l LOG_FILE\t\tSpecifies the log file to write.\n"
                  << "\t-u \t\tDisable the content updater.\n"
                  << "\nExample:"
                  << "\n\t./vd_scanner_testtool -c config.json\n"
                  << "\n\t./vd_scanner_testtool -c config.json -l log.txt\n"
                  << "\n\t./vd_scanner_testtool -c config.json -t template.json\n"
                  << "\n\t./vd_scanner_testtool -c config.json -t template.json -d\n"
                  << "\n\t./vd_scanner_testtool -c config.json -t template.json -s 10\n"
                  << "\n\t./vd_scanner_testtool -c config.json -t template.json -s 10 -r -b fakeAgentData.json\n"
                  << "\n\t./vd_scanner_testtool -c config.json -t template.json -s 5 -i a.json,b.json,c.json\n"
                  << "\n\t./vd_scanner_testtool -c config.json -t template.json -s 10 -r -b fakeAgentData.json -g "
                     "fakeGlobalData.json\n"
                  << std::endl;
    }

private:
    static std::string paramValueOf(const int argc,
                                    const char* argv[],
                                    const std::string& switchValue,
                                    const std::pair<bool, std::string>& required = std::make_pair(true, ""))
    {
        for (int i = 1; i < argc; ++i)
        {
            const std::string currentValue {argv[i]};

            if (currentValue == switchValue && i + 1 < argc)
            {
                // Switch found
                return argv[i + 1];
            }
        }

        if (required.first)
        {
            throw std::runtime_error {"Switch value: " + switchValue + " not found."};
        }

        return required.second;
    }

    static bool paramExists(const int argc,
                            const char* argv[],
                            const std::string& switchValue,
                            const std::pair<bool, bool>& required = std::make_pair(false, false))
    {
        for (int i = 1; i < argc; ++i)
        {
            const std::string currentValue {argv[i]};

            if (currentValue == switchValue)
            {
                return true;
            }
        }

        if (required.first)
        {
            throw std::runtime_error {"Switch value: " + switchValue + " not found."};
        }

        return required.second;
    }

    static std::vector<std::string> splitActions(const std::string& values)
    {
        if (values.empty())
        {
            return {};
        }

        std::vector<std::string> actionsValues;
        std::stringstream ss {values};

        while (ss.good())
        {
            std::string substr;
            getline(ss, substr, ','); // Getting each string between ',' character
            actionsValues.push_back(std::move(substr));
        }

        return actionsValues;
    }

    const std::string m_configurationFilePath;
    const std::string m_templateFilePath;
    const std::string m_waitTime;
    const std::vector<std::string> m_inputFiles;
    const bool m_onlyDownloadContent;
    const bool m_disableContentUpdater;
    const bool m_fakeReportServer;
    const std::string m_fakeDBServer;
    const std::string m_logFilePath;
    const std::string m_fakeDBGlobal;
    const std::string m_fakeDBPkgs;
    const std::string m_fakeDBHotfixes;
};

#endif // _CMD_ARGS_PARSER_HPP_
